﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.LoadTesting;
using Ramp.Service.Contract;

namespace LoadTestRamp.Plugin
{
    /// <summary>
    /// Load Test Ramp Plugin class that controls and helps in Ramping Up or Down the user load for a given scenario.
    /// </summary>
    public class LoadTestRamp : ILoadTestPlugin
    {
        #region Properties

        /// <summary>
        /// Property to hold the RampService HostName
        /// </summary>
        private string RampServiceHostName;

        /// <summary>
        /// Gets or sets the RampService hostname or IP where the WCF RampService is currently running on.
        /// </summary>
        /// <value>
        /// The RampService hostname or IP.
        /// </value>
        [DisplayName("Ramp Windows Service Host"), Description("Host name or ip of Ramp Windows Service"), HostnameDefaultValueAttribute("localhost")]
        [Category("Ramp Service")]
        public string RampServiceHost
        {
            get
            {   
                if (string.IsNullOrWhiteSpace(this.RampServiceHostName))
                {
                    // default
                    //return "LOCALHOST";
                    return System.Environment.GetEnvironmentVariable("COMPUTERNAME"); 
                }
                else
                {
                    return this.RampServiceHostName;
                }
            }

            set
            {
                if (value != null)
                {
                    this.RampServiceHostName = value;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private string RampServicePortNumber;

        /// <summary>
        /// Gets or sets the RampService port number where the WCF RampService is listening.
        /// </summary>
        /// <value>
        /// The RampService port number.
        /// </value>
        [DisplayName("Ramp Windows Service Port"), Description("Ramp Windows Service listening Port number"), DefaultValue("9898")]
        [Category("Ramp Service")]
        public string RampServicePort
        {
            get
            {
                if (string.IsNullOrWhiteSpace(this.RampServicePortNumber))
                {
                    // default
                    return "9898";
                }
                else
                {
                    return this.RampServicePortNumber;
                }
            }

            set
            {
                if (value != null)
                {
                    this.RampServicePortNumber = value;
                }
            }
        }

        /// <summary>
        /// Property to hold the Agent Name where the Ramping up or down should be undertaken.
        /// </summary>
        private string RampServiceControllerName;

        /// <summary>
        /// Gets or sets the ramp service agent name where the actual work of Ramp up or down will be undertaken
        /// by this plugin. [This described functionality is not yet implemented]
        /// </summary>
        /// <value>
        /// The ramp service agent name.
        /// </value>
        [DisplayName("Controller Name"), Description("Controller Name which is by default the first agent"), HostnameDefaultValueAttribute("localhost")]
        [Category("LoadTest")]
        public string RampServiceController
        {
            get
            {
                if (string.IsNullOrWhiteSpace(this.RampServiceControllerName))
                {
                    // default
                    //return "localhost";
                    return System.Environment.GetEnvironmentVariable("COMPUTERNAME");
                }
                else
                {
                    return this.RampServiceControllerName;
                }
            }

            set
            {
                if (value != null)
                {
                    this.RampServiceControllerName = value;
                }
            }
        }

        /// <summary>
        /// Holds the Visual Studio Process ID
        /// </summary>
        private int VSPID = 0;

        /// <summary>
        /// Holds the current loadtest object reference.
        /// </summary>
        private LoadTest ramperLoadTest;

        /// <summary>
        /// Member variable to ChannelFactory to get RampService object proxy.
        /// </summary>
        private ChannelFactory<IRampService> rampChannelFactory;

        /// <summary>
        /// Member variable to hold the actuall RampService Proxy Object.
        /// </summary>
        private IRampService remoteProxy;

        #endregion

        /// <summary>
        /// Initializes the load test plug-in.
        /// </summary>
        /// <param name="loadTest">The loadtest object.</param>        
        public void Initialize(LoadTest loadTest)
        { 
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: In LoadTest Initialize - 1");
            
            if (loadTest == null)
            {
                throw new ArgumentNullException("loadTest");
            }            

            this.ramperLoadTest = loadTest;

            // Not sure why the below is not working currently. Something to do with LoadFrom context on which 
            // the LoadTest Plugin is being called by the Visual Studio devenv.exe. If the RampServiceContract.dll
            // is installed into the GAC, we don't have to worry about resolving assembly or using ILMerge to merge
            // this assembly with its dependency WCF Contract object (RampServiceContract.dll).
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(Resolver);

            if ( (this.ramperLoadTest.Context.AgentId == 1) || 
                 (string.Compare(this.ramperLoadTest.Context.AgentName, 
                                 this.RampServiceController.Trim(), StringComparison.OrdinalIgnoreCase) == 0)) // Main Agent
            {
                //System.Diagnostics.Debug.WriteLine("LoadTestRamper: In LoadTest Initialize - 2");
                
                this.VSPID = System.Diagnostics.Process.GetCurrentProcess().Id;

                //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Resolve: All Done");
                
                this.ramperLoadTest.Heartbeat += new EventHandler<HeartbeatEventArgs>(this.Heartbeat);
                this.ramperLoadTest.LoadTestFinished += new EventHandler(this.LoadTestFinished);
                this.ramperLoadTest.LoadTestAborted += new EventHandler<LoadTestAbortedEventArgs>(this.LoadTestAborted);

                //System.Diagnostics.Debug.WriteLine("LoadTestRamper: In LoadTest Initialize - 3");

                List<string> loadTestScenario = new List<string>();
                List<LoadTestScenario> scenarios = (List<LoadTestScenario>)this.ramperLoadTest.Scenarios;                
                foreach (LoadTestScenario scene in scenarios)
                {
                    loadTestScenario.Add(scene.Name + "::" + scene.CurrentLoad);
                }
                                                
                //loadTestScenario = Array.ConvertAll(this.ramperLoadTest.Scenarios.ToArray(), s => string.Concat(((LoadTestScenario)s).Name, "::", ((LoadTestScenario)s).CurrentLoad)).ToList();

                this.GetRampServiceProxy();

                if (this.remoteProxy != null)
                {
                    this.remoteProxy.SetLoadTestScenarios(loadTestScenario);
                }
            }
        }

        public static Assembly Resolver(object sender, ResolveEventArgs args)
        {            
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Resolve: [" + args.Name + "]");
            var fileName = "LoadTestRampPlugin.RampServiceContract.dll";
            Assembly a1 = Assembly.GetExecutingAssembly();
            Stream s = a1.GetManifestResourceStream(fileName);
            if (s == null)
            {
                //System.Diagnostics.Debug.WriteLine("LoadTestRamper: UnResolved: [" + fileName + "]");
                throw new FileNotFoundException("LoadTestRamper: Cannot find assembly in the embedded resource.", fileName);
            }
            byte[] block = new byte[s.Length];
            s.Read(block, 0, block.Length);
            Assembly a2 = Assembly.Load(block);
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Resolved Assembly: [" + a2.FullName + "]");
            return a2;
        }

        /// <summary>
        /// Gets the ramp service proxy.
        /// </summary>
        public void GetRampServiceProxy()
        {
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Attempt to get Ramp Service Proxy");
            string serviceUri = "net.tcp://" + this.RampServiceHost + ":" + this.RampServicePort + "/RampService";
            NetTcpBinding tcpBinding = new NetTcpBinding();
            tcpBinding.TransactionFlow = false;
            tcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
            tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
            tcpBinding.Security.Mode = SecurityMode.None;

            EndpointAddress endPointAddress = new EndpointAddress(serviceUri);

            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Attempt to connect to: " + serviceUri);
            try
            {
                this.rampChannelFactory = new ChannelFactory<IRampService>(tcpBinding, endPointAddress);
                this.remoteProxy = rampChannelFactory.CreateChannel();
            }
            catch (CommunicationException) { }
            catch (System.Net.Sockets.SocketException) { }
            catch (System.Exception) { }

            //if (this.remoteProxy != null)
            //{
            //    //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Got Proxy Object.");
            //}
            //else
            //{
            //    //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Proxy Object is null.");
            //}
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        protected void Dispose()
        {
            this.ramperLoadTest.Heartbeat -= new EventHandler<HeartbeatEventArgs>(this.Heartbeat);
            this.ramperLoadTest.LoadTestFinished -= new EventHandler(this.LoadTestFinished);
            this.ramperLoadTest.LoadTestAborted -= new EventHandler<LoadTestAbortedEventArgs>(this.LoadTestAborted);

            if (this.remoteProxy != null)
            {
                ((IClientChannel)this.remoteProxy).Close();
                this.rampChannelFactory.Close();
            }
        }

        #region Event Handlers
        /// <summary>
        /// This method is invoked by LoadTest every second.
        /// Not Implemented in this version at this level.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">The <see cref="Microsoft.VisualStudio.TestTools.LoadTesting.HeartbeatEventArgs"/> instance containing the event data.</param>
        protected void Heartbeat(object sender, HeartbeatEventArgs e)
        {
            if (e.IsWarmupComplete)
            {
                if (this.remoteProxy != null)
                {                    
                    this.remoteProxy.SetLoadTestRunningStatus(true);
                    if (this.remoteProxy.GetScenarioChanged())
                    {
                        //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Change to current load is requested.");
                        List<string> updatedScenarios = this.remoteProxy.GetLoadTestScenarios();
                        foreach (string scenario in updatedScenarios)
                        {
                            string[] scenarioArray = scenario.Split(new string[] { "::" }, StringSplitOptions.None);
                            string scenarioName = scenarioArray[0];
                            string loadValue = scenarioArray[1];
                            int adjustLoad = 0;
                            bool parsed = Int32.TryParse(loadValue, out adjustLoad);
                            if (parsed)
                            {
                                LoadTestScenario loadTestScenario = this.ramperLoadTest.Scenarios.First(s => s.Name == scenarioName);
                                int newLoad = loadTestScenario.CurrentLoad + (adjustLoad);
                                if(newLoad >= 1)
                                {
                                    loadTestScenario.CurrentLoad = newLoad;
                                    //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Updated CurrentLoad to: " + newLoad);
                                }
                            }
                        }
                        this.remoteProxy.SetScenarioChanged(false);
                    }
                }
                else
                {
                    //System.Diagnostics.Debug.WriteLine("LoadTestRamper: Unable to get hold of proxy object");
                    this.remoteProxy.SetLoadTestRunningStatus(false);
                }
            }
        }

        /// <summary>
        /// This method is invoked by LoadTest at the very end of the LoadTest scenarios.
        /// Ideal for tear-down or finalising things.
        /// </summary>
        /// <param name="sender">Object sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void LoadTestFinished(object sender, EventArgs e)
        {
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: In LoadTestFinished");
            if (this.remoteProxy != null)
            {
                this.remoteProxy.SetLoadTestRunningStatus(false);
            }
        }

        /// <summary>
        /// This method is invoked by LoadTest when the test execution is aborted either by system or user.
        /// Can call the tear-down, just to clean things up for next run.
        /// </summary>
        /// <param name="sender">Object sender.</param>        
        /// <param name="e">The <see cref="Microsoft.VisualStudio.TestTools.LoadTesting.LoadTestAbortedEventArgs"/> instance containing the event data.</param>        
        protected void LoadTestAborted(object sender, LoadTestAbortedEventArgs e)
        {
            //System.Diagnostics.Debug.WriteLine("LoadTestRamper: In LoadTestAborted");
            if (this.remoteProxy != null)
            {
                this.remoteProxy.SetLoadTestRunningStatus(false);
            }
        }
        #endregion
    }
}
